package it.unibo.refolding.pico;

import it.unibo.refolding.pico.transformers.Transformers;

import java.awt.Color;
import java.awt.Shape;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.google.common.collect.ImmutableMap;

/**
 * All floating point numbers are in the standard units (meters, Volts, Newton
 * etc).
 */
public abstract class PicoData {

  public final double xOffsetM;
  public final double springConstantNPerM;
  protected final double sensZSensMPerV;
  /*
   * 3.0517580000000003E-10 > 0
   */
  public final double zLsbToMetresFactor;
  public final double deflectionLimitV;
  public final double deflectionSensitivityMPerV;

  final double zScanStartHardValueV;

  public PicoData(double xOffsetM, double springConstantNPerM, double sensZSensMPerV,
    double zScanStartHardValueV, double zLsbToMetresFactor, double deflectionLimitV,
    double deflectionSensitivityMPerV) {
    this.xOffsetM = xOffsetM;
    this.springConstantNPerM = springConstantNPerM;
    this.sensZSensMPerV = sensZSensMPerV;
    this.zScanStartHardValueV = zScanStartHardValueV;
    this.zLsbToMetresFactor = zLsbToMetresFactor;
    this.deflectionLimitV = deflectionLimitV;
    this.deflectionSensitivityMPerV = deflectionSensitivityMPerV;
  }

  public double deflectionLsbToMetresFactor() {
    return deflectionSensitivityMPerV * Transformers.voltLsb(deflectionLimitV);
  }

  @Override
  public boolean equals(Object o) {
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    PicoData other = (PicoData) o;
    return xOffsetM == other.xOffsetM & springConstantNPerM == other.springConstantNPerM
      & sensZSensMPerV == other.sensZSensMPerV & zLsbToMetresFactor == other.zLsbToMetresFactor
      & deflectionLimitV == other.deflectionLimitV
      & deflectionSensitivityMPerV == other.deflectionSensitivityMPerV
      & zScanStartHardValueV == other.zScanStartHardValueV;
  }

  public abstract AbstractDirectionData sharedApproach();

  public abstract short[] copyOfFullDeflectionArray();

  public abstract short[] copyOfFullZArray();

  public double deflectionLsbToNewtonsFactor() {
    return deflectionLsbToMetresFactor() * springConstantNPerM;
  }

  public double deltaZScanVForDeltaForceN(double deltaForceN) {
    double deltaM = deltaForceN / springConstantNPerM;
    double result = deltaM / sensZSensMPerV;
    return result;
  }

  public double[] copyOfApproachForces() {
    double[] a = new double[sharedApproach().deflection.length];
    for (int i = sharedApproach().deflection.length; --i >= 0;) {
      a[i] = deflectionLsbToNewtonsFactor() * sharedApproach().deflection[i];
    }
    return a;
  }

  public double actualZApproachRangeMetres() {
    return zLsbToMetresFactor * sharedApproach().actualZRange();
  }

  public List<Shape> drawApproachShapes(double svgDefScale) {
    return sharedApproach().draw(svgDefScale);
  }

  public double zScanStartMetres() {
    return zScanStartHardValueV * sensZSensMPerV;
  }

  public Map<Color, List<Shape>> draw(double svgDefScale) {
    return ImmutableMap.of(Color.BLACK, drawApproachShapes(svgDefScale));
  }

  public abstract short[] minMaxDeflection();

  public abstract PicoData withSpringConstant(double fixedSpringConstantNPerM);

  public abstract PicoData withDeflectionSensitivity(double fixedDeflectionSensitivityMPerV);

  public abstract PicoData withZScanStartM(double zScanStartM);

  public static List<PicoData> withDeflectionSensitivity(
    List<? extends PicoData> a, double fixedDeflectionSensitivityMPerV) {
    List<PicoData> b = new ArrayList<>(a.size());
    for (PicoData picoData : a) {
      b.add(picoData.withDeflectionSensitivity(fixedDeflectionSensitivityMPerV));
    }
    return b;
  }

  public static List<PicoData> withSpringConstant(
    List<? extends PicoData> a, double fixedSpringConstantNPerM) {
    List<PicoData> b = new ArrayList<>(a.size());
    for (PicoData picoData : a) {
      b.add(picoData.withSpringConstant(fixedSpringConstantNPerM));
    }
    return b;
  }
}
